use std::fs;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use openssl::asn1::Asn1Time;
use openssl::hash::MessageDigest;
use openssl::nid::Nid;
use openssl::pkey::{PKey, Private};
use openssl::rsa::Rsa;
use openssl::x509::{X509, X509Builder, X509Name, X509NameBuilder, X509NameRef, X509Req, X509ReqBuilder};

// 注意：在运行此演示之前，应先在您的系统上安装 openssl 库。
// openssl 使用参考文档 https://docs.rs/openssl/latest/openssl/all.html

/// 创建证书签名请求
fn create_csr(pkey: &PKey<Private>, subject_name: &X509NameRef) -> X509Req {
    // 新建一个证书请求的构建器。
    // 创建一个新的 X.509 证书签名请求 (Certificate Signing Request, CSR) 构建器，以便于构建一个证书签名请求
    // 注意：CSR 仅包含公钥信息，不包含私钥信息。
    // 一旦有了这个构建器，你可以向其中添加各种信息，例如主体名（subject name）、公钥等，最终构建出一个完整的 CSR。
    let mut builder = X509ReqBuilder::new().unwrap();
    // 设置证书请求的版本。
    // 版本 1 代表 v1 证书请求，版本 2 代表 v3 证书请求。
    // 版本 3 证书请求包含更多的扩展，如密钥用法、认证用途、CA 约束等。
    builder.set_version(2).unwrap();
    builder.set_subject_name(subject_name).unwrap();
    // 将公钥 (PKey) 设置到 X509ReqBuilder 或 X509Builder 实例中
    builder.set_pubkey(pkey).unwrap();
    builder.sign(pkey, MessageDigest::sha256()).unwrap();
    builder.build()
}


/// 创建自签名证书
// 当证书既是自己签发又是自己的时候，pkey 和 issuer_pkey 就是相同的。
// 例如，在测试环境中，可能不需要一个外部的信任机构来签发证书，而是使用一个自签名的证书。
fn create_self_signed_certificate(server_pkey: &PKey<Private>, server_subject_name: &X509Name) -> X509 {
    create_certificate(server_pkey, server_subject_name, server_pkey, server_subject_name)
}


/// 创建证书
// client_pkey 和 server_pkey 是不同的。client_pkey 属于证书申请人，而 server_pkey 属于证书签发者（通常是CA）。
// CA使用其私钥来签署申请人的公钥证书，从而证明该公钥属于特定的申请人。
// 但如果是在创建自签名证书的情况下，则 server_pkey 就是 client_pkey。

// client_pkey: 这是指要绑定到证书的公钥/私钥对。这个密钥对将会被用来加密数据或验证签名等操作。这是证书持有者的密钥。
// server_pkey: 这是指用来签署证书的公钥/私钥对。这个密钥对属于证书的签发者（通常是证书颁发机构或CA）。这是用来验证证书有效性的密钥。
// 在此处，client_pkey 是申请人的公钥，server_pkey 是 CA 的私钥。
// issuer_name: 这是指证书签发者的名称。如证书颁发机构（CA）
// subject_name: 这是指证书申请人的名称。如申请人
// 注意：在创建自签名证书的情况下，issuer_name 就是 subject_name。
fn create_certificate(
    client_pkey: &PKey<Private>,
    client_subject_name: &X509Name,
    server_pkey: &PKey<Private>,
    server_subject_name: &X509Name,
) -> X509 {
    let now = Asn1Time::days_from_now(0).unwrap();
    let expire = Asn1Time::days_from_now(365).unwrap();
    let mut cert_builder = X509Builder::new().unwrap();
    // 设置证书的版本。
    // 版本 1 代表 v1 证书，版本 2 代表 v3 证书。
    // 版本 3 证书包含更多的扩展，如密钥用法、认证用途、CA 约束等。
    // 版本 2 证书是最新的版本，建议使用版本 2 证书。
    // 注意：版本 1 证书不包含密钥用法、认证用途、CA 约束等扩展，不建议使用。
    cert_builder.set_version(2).unwrap();
    // 设置证书的有效期，从现在开始，365 天后过期
    cert_builder.set_not_before(&now).unwrap();
    cert_builder.set_not_after(&expire).unwrap();
    // 证书颁发这是证书的签发者，也就是 CA 的名称。服务端向客户端签发证书，相当于是服务端授权客户端
    cert_builder.set_issuer_name(server_subject_name).unwrap();
    // 证书的主体是申请人的名称。
    cert_builder.set_subject_name(client_subject_name).unwrap();
    // 证书的公钥是申请人的公钥。
    cert_builder.set_pubkey(client_pkey).unwrap();
    // 证书签名是由 CA 的私钥签名的。
    cert_builder.sign(server_pkey, MessageDigest::sha256()).unwrap();
    // 获取证书，消耗 cert_builder 证书建造者，返回证书。
    cert_builder.build()
}

pub fn certificate_demo() {
    // 生成服务端 RSA 密钥对
    // 生成一个 2048 位的 RSA 密钥对。Rsa 类型代表一个 RSA 密钥对象，它可以是公钥或私钥。
    let server_rsa = Rsa::generate(2048).unwrap();
    // PKey 类型代表了一个通用的公钥对象，它可以是任何类型的公钥，比如 RSA、ECDSA 等等
    // from_rsa 方法从一个给定的 Rsa 对象创建一个 PKey 对象。由于 rsa 包含了公钥和私钥，所以 PKey 对象实际上是包含公钥和私钥的。
    // 这样做的目的是为了后续操作，例如生成证书签名请求（CSR）或者直接生成自签名证书。
    let server_pkey = PKey::from_rsa(server_rsa).unwrap();

    // 创建一个新的 X.509 名称构建器，用于构建证书的主题名称。它包含了证书所有者的信息。这些信息通常用于标识证书的所有者，确保证书的持有者是他们声称的人或实体。
    // 注意：主题名称必须包含 Common Name (CN) 字段，否则无法创建有效的证书。
    // 你可以添加其他字段，如 Organization Name (O)、Organizational Unit Name (OU)、Locality Name (L)、
    let mut server_subject_name = X509NameBuilder::new().unwrap();
    // 添加 Common Name (CN) 通常是域名或主机名，用于标识证书的持有者
    server_subject_name.append_entry_by_nid(Nid::COMMONNAME, "server.example.com").unwrap();
    // 添加 Organization Name (O) 组织的正式名称
    server_subject_name.append_entry_by_nid(Nid::ORGANIZATIONNAME, "Example Inc.").unwrap();
    // 添加 Organizational Unit Name (OU) 组织内的部门或单位名称
    server_subject_name.append_entry_by_nid(Nid::ORGANIZATIONALUNITNAME, "IT Department").unwrap();
    // 添加 Locality Name (L) 证书持有者的所在地城市或城镇
    server_subject_name.append_entry_by_nid(Nid::LOCALITYNAME, "New York").unwrap();
    // 添加 State or Province Name (ST) 证书持有者的州或省份
    server_subject_name.append_entry_by_nid(Nid::STATEORPROVINCENAME, "New York").unwrap();
    // 添加 Country Name (C) 证书持有者的国家代码
    server_subject_name.append_entry_by_nid(Nid::COUNTRYNAME, "US").unwrap();
    // 创建服务端的主体名称
    let server_subject_name = server_subject_name.build();

    // 创建服务端的 CSR 服务器自签名时可以用来传递其内的 server_subject_name 等信息，也可以不使用 CSR 直接传递 server_subject_name
    // 证书签名请求（CSR，Certificate Signing Request）主要用于在创建证书时向证书颁发机构（CA）传递申请人的信息。
    // CSR 包含了申请证书的实体的公钥和一些元数据，例如组织名称、域名等，这些信息会被包含在最终签发的数字证书中。
    let server_csr = create_csr(&server_pkey, &server_subject_name);
    // 创建服务端的自签名证书
    /*
    服务器使用自签名证书的情况通常发生在不需要第三方权威机构验证身份的情况下，或者是在开发测试环境中为了节省成本而采用的一种做法。下面是一些使用自签名证书的原因：
    1. 节省成本：在开发测试环境中，可以不用购买证书，而是自己生成一个自签名证书，并使用它来进行 HTTPS 通信。
    2. 快速部署：在开发测试环境中，可以快速部署 HTTPS 服务，而无需等待证书颁发机构的审核。
    3. 隐私保护：在开发测试环境中，可以不用担心隐私数据泄露，因为所有通信都是加密的。
    尽管自签名证书在上述情况下有其用途，但它们也有一些缺点：
    1. 信任问题：自签名证书没有第三方 CA 的背书，所以浏览器和其他客户端通常会警告用户该证书不受信任。
    2. 管理负担：如果使用自签名证书，则需要确保所有客户端都正确配置信任这些证书，这可能会增加管理和维护的工作量。
    3. 安全性风险：由于任何人都可以生成自签名证书，因此存在冒充合法站点的风险，尤其是对于那些不熟悉如何检查证书有效性的用户。
    因此，在生产环境中，尤其是在面向公众的服务中，通常推荐使用由知名 CA 签发的证书，以提高信任度并避免安全警告。
    */
    let server_cert = create_self_signed_certificate(&server_pkey, &server_subject_name);




    // 生成客户端 RSA 密钥对
    let client_rsa = Rsa::generate(2048).unwrap();
    let client_pkey = PKey::from_rsa(client_rsa).unwrap();

    // 创建客户端的主体名称
    let mut client_subject_name = X509NameBuilder::new().unwrap();
    client_subject_name.append_entry_by_nid(Nid::COMMONNAME, "client.example.com").unwrap();
    client_subject_name.append_entry_by_nid(Nid::ORGANIZATIONNAME, "Example Inc.").unwrap();
    client_subject_name.append_entry_by_nid(Nid::ORGANIZATIONALUNITNAME, "IT Department").unwrap();
    client_subject_name.append_entry_by_nid(Nid::LOCALITYNAME, "New York").unwrap();
    client_subject_name.append_entry_by_nid(Nid::STATEORPROVINCENAME, "New York").unwrap();
    client_subject_name.append_entry_by_nid(Nid::COUNTRYNAME, "US").unwrap();
    let client_subject_name = client_subject_name.build();

    // 创建客户端的 CSR
    let client_csr = create_csr(&client_pkey, &client_subject_name);
    // 创建客户端证书，使用服务端的私钥签名
    let client_cert = create_certificate(&client_pkey, &client_subject_name, &server_pkey, &server_subject_name);


    // let path = Path::new("/tmp/foo. rs");
    // 假设您有一个文件路径
    let base_path = PathBuf::from("/path/to/directory");
    // 假设您有一个变量表示文件名
    let file_name = "example.txt";
    // 将文件名拼接到基础路径中
    let full_path = base_path.join(file_name);
    // 输出完整的文件路径
    println!("Full path: {}", full_path.display());

    // 获取当前文件的路径
    let file_path = file!();    // 当前文件路径: rust-jwt/src/main.rs
    println!("当前文件路径: {}", file_path);
    // 将文件路径转换为 PathBuf 对象
    let path_buf = PathBuf::from(file_path);
    // 获取文件所在的目录
    if let Some(parent_dir) = path_buf.parent() {
        println!("当前文件所在目录: {}", parent_dir.display());    // 当前文件所在目录: rust-jwt/src
        // 拼接证书存放目录
        let cert_dir = parent_dir.join("cert");
        println!("拼接后的证书存放目录: {}", cert_dir.display());
        // 只能创建单个目录
        match fs::create_dir(&cert_dir) {
            Ok(()) => {
                println!("项目根目录下创建cert证书存放目录成功！");

                // 写入私钥和证书到文件
                // 创建文件不能使用以 / 开头的绝对路径，只能使用相对路径
                let server_key_path = &cert_dir.join("server.key");
                let mut file = File::create(server_key_path).unwrap();
                file.write_all(&*server_pkey.private_key_to_pem_pkcs8().unwrap()).unwrap();

                let server_crt_path = &cert_dir.join("server.crt");
                let mut file = File::create(server_crt_path).unwrap();
                file.write_all(&*server_cert.to_pem().unwrap()).unwrap();

                let client_key_path = &cert_dir.join("client.key");
                let mut file = File::create(client_key_path).unwrap();
                file.write_all(&*client_pkey.private_key_to_pem_pkcs8().unwrap()).unwrap();

                let client_csr_path = &cert_dir.join("client.csr");
                let mut file = File::create(client_csr_path).unwrap();
                file.write_all(&*client_cert.to_pem().unwrap()).unwrap();
            },
            Err(e) => eprintln!("项目根目录下创建目录失败: {}", e),
        }
    } else {
        println!("无法确定当前文件所在目录");
    }
}

// 相当于执行以下命令：
/*
# 生成一个证书签名请求 (CSR)
openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr
# 使用自签名的方式签发该 CSR 生成证书
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
*/


/*
在构建 X.509 证书时，主题内容（Subject）是一个非常重要的组成部分，它包含了证书所有者的信息。这些信息通常用于标识证书的所有者，确保证书的持有者是他们声称的人或实体。
主题内容（Subject）是一个 X.509 证书中的 Distinguished Name (DN)，通常包含以下几个字段：
1. **Common Name (CN)**: 通常是域名或主机名，用于标识证书的持有者。例如，`www.example.com`。
2. **Organization Name (O)**: 组织的正式名称。例如，`Example Inc.`。
3. **Organizational Unit Name (OU)**: 组织内的部门或单位名称。例如，`IT Department`。
4. **Locality Name (L)**: 证书持有者的所在地城市或城镇。例如，`New York`。
5. **State or Province Name (ST)**: 证书持有者的州或省份。例如，`New York`。
6. **Country Name (C)**: 证书持有者的国家代码，通常为两位字母代码。例如，`US`。
7. **Serial Number (SN)**: 通常不用于 X.509 证书的主题，但在某些情况下可能会出现。
8. **Street Address (STREET)**: 街道地址，不是必需的。
9. **Postal Code (POSTAL_CODE)**: 邮政编码，不是必需的。
10. **Title (TITLE)**: 职位或职称，不是必需的。
11. **Email Address (EMAIL)**: 电子邮件地址，不是必需的。

在这个例子中，我们创建了一个 `X509NameBuilder` 实例，并使用 `append_entry_by_nid` 方法来添加各个字段。每个字段都通过相应的 `Nid`（命名标识符）来指定。
### 注意事项
- **字段顺序**：在构建主题名称时，字段的顺序并不重要，但是通常会按照一定的约定来组织这些字段。
- **字段限制**：某些字段可能有长度限制。例如，`Common Name` 通常限制在 64 个字符以内。
- **可选字段**：某些字段如 `Organizational Unit Name`, `Locality Name`, `State or Province Name`, `Email Address` 等可以根据需要选择性地添加。
- **多值字段**：某些字段可以包含多个值，例如多个 `Organizational Unit Name`。
确保在设置这些字段时遵循最佳实践和标准指南，以确保证书的有效性和可信度。如果你需要更具体的帮助或有其他问题，请随时告诉我！
*/